﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
#if LIVE
using Microsoft.Xna.Framework.GamerServices;
#endif
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#if WINDOWS_PHONE
using Microsoft.Xna.Framework.Input.Touch;
#endif
#if WINDOWS
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
#endif
using Microsoft.Xna.Framework.Media;
using Neat;
using Neat.MenuSystem;
using Neat.EasyMenus;
using Neat.GUI;
using Neat.Mathematics;
using Neat.Graphics;
using Neat.Components;
using System.Diagnostics;
using Microsoft.Kinect;

namespace Kinectoid
{
    public class KinectoidScreen : Screen
    {
        #region FIELDS
        KinectEngine kinect;
        LineBrush lb;
        Label lblScore1, lblScore2;

        int p1Id, p2Id; 
        float l1x, l2x, l3x, l4x;
        Vector2 pos1, pos2;
        int score1, score2;
        Polygon pad1, pad2, pad3, pad4;
        Vector2 size;
        Vector2 vel;
        Polygon ball;
        List<Block> blocks = new List<Block>();

        float kinectScale = 2f;
        float gameTop = 50;
        int blocksInLine = 10;
        bool twoPlayer = false;
        bool god = false;
        bool pauseMechanics = true;
        Vector2 blockOffset = new Vector2(8);
        Vector2 firstBlockPos = new Vector2(100, 100);
        string levelData =
            //0123456789
            "ssssssssss" +
            "rrrrrrrrrr" +
            "gggggggggg" +
            "bbbbbbbbbb" +
            "yyyyyyyyyy";
        #endregion

        #region Initialize
        public KinectoidScreen(NeatGame Game)
            : base(Game)
        {
        }

        public override void LoadContent()
        {
            base.LoadContent();
            lb = new LineBrush(game.GraphicsDevice, 2);
        }

        public override void Initialize()
        {
            base.Initialize();

            lblScore1 = Form.NewControl("lblScore1", new Label()).ToLabel();
            lblScore1.Position = new Vector2(10);
            lblScore1.Caption = "";

            lblScore2 = Form.NewControl("lblScore2", new Label()).ToLabel();
            lblScore2.Position = new Vector2(game.GameWidth - 200, 10);
            lblScore2.Caption = "";

            game.Console.AddCommand("g_twoplayer", g_twoplayer);
        }

        void g_twoplayer(IList<string> args)
        {
            twoPlayer = bool.Parse(args[1]);
        }

        bool oldTouch; 
        public override void Activate()
        {
            base.Activate();
            size = new Vector2(75, 16);
            kinect = ((KinectoidGame)game).Kinect;
            Reset();
            oldTouch = game.Touch.Enabled;
            game.Touch.Enabled = false;
        }

        public override void Deactivate(string nextScreen)
        {
            game.Touch.Enabled = oldTouch;
            base.Deactivate(nextScreen);
        }

        void Reset()
        {
            ResetLevel();
            pos1 = new Vector2((game.GameWidth - size.X) / 2.0f, game.GameHeight - 100);
            pos2 = new Vector2(pos1.X + size.X, game.GameHeight - 100 + size.Y);

            ResetBall();
            
            score1 = 0;
            score2 = 0;

            pad1 = Polygon.BuildRectangle(pos1.X, pos1.Y, size.X, size.Y);
            pad2 = Polygon.BuildRectangle(pos1.X, pos1.Y, size.X, size.Y);

            pad3 = Polygon.BuildRectangle(pos2.X, pos2.Y, size.X, size.Y);
            pad4 = Polygon.BuildRectangle(pos2.X, pos2.Y, size.X, size.Y);
        }

        void ResetBall()
        {
            ball = //Polygon.BuildCircle(20, pos1 - new Vector2(0, 10), 5);
                Polygon.BuildRectangle(pos1.X + size.X / 2.0f - 5, pos1.Y - 30, 10, 10);
            vel = Vector2.Zero;
            vel.X = game.RandomGenerator.Next(1, 5) * ((game.RandomGenerator.Next(0,50) % 2)*2 - 1);
            vel.Y = -5;
            ball.AutoTriangulate = true;
            ball.Triangulate();
        }

        void ResetLevel()
        {
            blocks.Clear();
            int r = -1;
            Vector2 pos = firstBlockPos;
            Vector2 blockOffset = this.blockOffset + new Vector2(100, 24);
            for (int i = 0; i < levelData.Length; i++)
            {
                if (i % blocksInLine == 0) 
                {
                    r++;
                    pos.Y += blockOffset.Y;
                    pos.X = firstBlockPos.X;
                }
                BlockTypes type = BlockTypes.None;
                Dictionary<char, BlockTypes> charType = new Dictionary<char, BlockTypes>
                {
                    {'s', BlockTypes.Solid},
                    {'r', BlockTypes.Red},
                    {'g', BlockTypes.Green},
                    {'b', BlockTypes.Blue},
                    {'y', BlockTypes.Yellow},
                    {'p', BlockTypes.Pink}
                };
                if (charType.ContainsKey(levelData[i])) type = charType[levelData[i]];

                if (type != BlockTypes.None)
                {
                    blocks.Add(new Block(game, pos)
                    {
                         BlockType = type
                    }
                    );
                }
                pos.X += blockOffset.X;
            }
        }
        #endregion

        #region Update
        public override void Behave(GameTime gameTime)
        {
            if (!pauseMechanics) ball.Offset(vel);

            Vector2 mtd;
            var aabb = ball.GetBoundingRect();

            //
            // Keep the ball in the screen
            //
            if (aabb.X < 0) //LEFT
            {
                ball.Offset(new Vector2(-aabb.X, 0));
                vel.X *= -1;
            }
            if (aabb.Y < gameTop) //TOP
            {
                ball.Offset(new Vector2(0, gameTop-aabb.Y));
                vel.Y *= -1;
            }
            if (aabb.Right > game.GameWidth) //RIGHT
            {
                ball.Offset(new Vector2(-aabb.Right + game.GameWidth, 0));
                vel.X *= -1;
            }
            if (aabb.Bottom > game.GameHeight) //BOTTOM
            {
                if (god)
                {
                    vel.Y *= -1;
                    ball.Offset(new Vector2(0, -aabb.Bottom + game.GameHeight));
                }
                else
                {
                    //DIE!
                    game.Console.Run("et_echo Oops!");
                    score1 -= 100;
                    score2 -= 100;
                    ResetBall();
                }
            }

            //
            // Check collision with paddles
            //
            if (Polygon.Collide(ball, pad1, out mtd))
            {
                var oldvel = vel;
                ball.Offset(mtd);
                vel.X = l1x * vel.Length();
                vel.Y = -Math.Abs(vel.Y); //Force up
                score1 += 5;
            }
            if (Polygon.Collide(ball, pad2, out mtd))
            {
                var oldvel = vel;
                ball.Offset(mtd);
                vel.X = l2x * vel.Length();
                vel.Y = -Math.Abs(vel.Y); //Force up
                score1 += 5;
            }
            if (twoPlayer)
            {
                if (Polygon.Collide(ball, pad3, out mtd))
                {
                    var oldvel = vel;
                    ball.Offset(-mtd);
                    vel.X = l3x * vel.Length();
                    vel.Y = -Math.Abs(vel.Y); //Force up
                    score2 += 5;
                }
                if (Polygon.Collide(ball, pad4, out mtd))
                {
                    var oldvel = vel;
                    ball.Offset(-mtd);
                    vel.X = l4x * vel.Length();
                    vel.Y = -Math.Abs(vel.Y); //Force up
                    score2 += 5;
                }
            }

            //
            // Check collision with blocks
            //
            foreach (var item in blocks)
            {
                var oldvel = vel;
                if (item.Dead) continue;
                if (Polygon.RectanglesCollide(ball, item.Mesh, out mtd))
                {
                    ball.Offset(mtd);
                    if (Math.Abs(mtd.X) > 1.0f) vel.X *= -1;
                    if (Math.Abs(mtd.Y) > 1.0f) vel.Y *= -1;
                    item.Hit();

                    if (Math.Sign(vel.X) == Math.Sign(oldvel.X) && Math.Sign(vel.Y) == Math.Sign(oldvel.Y))
                    {
                        if (Math.Abs(mtd.X) > Math.Abs(mtd.Y)) vel.X *= -1;
                        else vel.Y *= -1;
                    }
                }
            }

            //
            // Update & remove dead blocks
            //
            for (int i = 0; i < blocks.Count; i++)
            {
                if (blocks[i].Dead)
                {
                    score1 += blocks[i].Score;
                    score2 += blocks[i].Score;
                    blocks.RemoveAt(i);
                }
                else blocks[i].Behave(gameTime);
            }

            //
            // Check for win
            //
            if (!twoPlayer && score1 < 0)
            {
                game.TextEffects.Echo("You Lose!");
                Reset();
            }
            if (blocks.Count == 0)
            {
                if (twoPlayer)
                {
                    if (score1 > score2) game.TextEffects.Echo("Player One Wins!");
                    else if (score1 < score2) game.TextEffects.Echo("Player Two Wins!");
                    else game.TextEffects.Echo("Game Over!");
                }
                else
                {
                    game.TextEffects.Echo("You Win!");
                    game.TextEffects.Echo("Score: " + score1);
                }
                Reset();
            }

            //
            // Limit ball's velocity
            //
            vel.X = Math.Min(vel.X, 8);
            vel.X = Math.Max(-8, vel.X);
            vel.Y = Math.Min(vel.Y, 8);
            vel.Y = Math.Max(-8, vel.Y);


            lblScore1.Caption = "Player 1: " + score1;
            lblScore2.Caption = "Player 2: " + score2;
            lblScore2.Visible = twoPlayer;

            base.Behave(gameTime);
        }

        
        public override void HandleInput(GameTime gameTime)
        {
            //
            // Track skeletons
            //
            pauseMechanics = kinect.TrackedSkeletonsCount == 0;
            if (kinect.TrackedSkeletonsCount > 0)
            {
                var center = (game.GameWidth - size.X) / 2.0f;

                p1Id = 0;
                if (kinect.TrackedSkeletonsCount > 1)
                {
                    if (kinect.ToVector3(JointType.Spine, 0).Z > kinect.ToVector3(JointType.Spine, 1).Z) p1Id = 1;
                    pauseMechanics = (kinect.GetInferredJointsCount(0) + kinect.GetInferredJointsCount(1)) > 10;
                }
                else
                    pauseMechanics = kinect.GetInferredJointsCount() > 5;
                
                p2Id = 1;
                if (p1Id == p2Id) p2Id = 0;

                pos1.X = MathHelper.Lerp(pos1.X, center + (kinect.ToVector2(JointType.Head, p1Id).X * kinectScale) * center, 0.1f);
                if (kinect.TrackedSkeletonsCount > 1)
                    pos2.X = MathHelper.Lerp(pos2.X, center + (kinect.ToVector2(JointType.Head, p2Id).X * kinectScale) * center, 0.1f);

                var hl = kinect.ToVector2(JointType.HandLeft, new Vector2(size.X, 128), p1Id);
                var hr = kinect.ToVector2(JointType.HandRight, new Vector2(size.X, 128), p1Id);

                //
                // Keep paddles in the screen
                //
                pos1.X = Math.Max(0, pos1.X);
                pos1.X = Math.Min(game.GameWidth - size.X, pos1.X);
                pos2.X = Math.Max(0, pos2.X);
                pos2.X = Math.Min(game.GameWidth - size.X, pos2.X);

                //
                //Paddle 1
                //
                var c = pos1 + hl;
                var H = kinect.ToVector2(JointType.HandLeft, new Vector2(100), p1Id);
                var L = H - kinect.ToVector2(JointType.ElbowLeft, new Vector2(100), p1Id);
                
                var N = Vector2.Normalize(GeometryHelper.Rotate2(L, MathHelper.PiOver2));
                var A = c - N * 0.5f * size.X;
                var B = A + N * size.X;
                var nL = Vector2.Normalize(L);

                l1x = nL.X;
                var C = nL * size.Y + B;
                var D = nL * size.Y + A;
                pad1 = new Polygon(new List<Vector2> { D, A, B, C });

                //
                //Paddle 2
                //
                c = pos1 + hr;
                H = kinect.ToVector2(JointType.HandRight, new Vector2(100), p1Id);
                L = H - kinect.ToVector2(JointType.ElbowRight, new Vector2(100), p1Id);
                N = Vector2.Normalize(GeometryHelper.Rotate2(L, MathHelper.PiOver2));
                A = c - N * 0.5f * size.X;
                B = A + N * size.X;
                nL = Vector2.Normalize(L);

                l2x = nL.X;
                C = nL * size.Y + B;
                D = nL * size.Y + A;
                pad2 = new Polygon(new List<Vector2> { D, A, B, C });

                if (twoPlayer && kinect.TrackedSkeletonsCount > 1)
                {
                    hl = kinect.ToVector2(JointType.HandLeft, new Vector2(size.X, 128), p2Id);
                    hr = kinect.ToVector2(JointType.HandRight, new Vector2(size.X, 128), p2Id);
                    //
                    //Paddle 3
                    //
                    c = pos2 + hl;
                    H = kinect.ToVector2(JointType.HandLeft, new Vector2(100), p2Id);
                    L = H - kinect.ToVector2(JointType.ElbowLeft, new Vector2(100), p2Id);
                    N = Vector2.Normalize(GeometryHelper.Rotate2(L, MathHelper.PiOver2));
                    A = c - N * 0.5f * size.X;
                    B = A + N * size.X;
                    nL = Vector2.Normalize(L);

                    l3x = nL.X;
                    C = nL * size.Y + B;
                    D = nL * size.Y + A;
                    pad3 = new Polygon(new List<Vector2> { D, A, B, C });

                    //
                    //Paddle 4
                    //
                    c = pos2 + hr;
                    H = kinect.ToVector2(JointType.HandRight, new Vector2(100), p2Id);
                    L = H - kinect.ToVector2(JointType.ElbowRight, new Vector2(100), p2Id);
                    N = Vector2.Normalize(GeometryHelper.Rotate2(L, MathHelper.PiOver2));
                    A = c - N * 0.5f * size.X;
                    B = A + N * size.X;
                    nL = Vector2.Normalize(L);

                    l4x = nL.X;
                    C = nL * size.Y + B;
                    D = nL * size.Y + A;
                    pad4 = new Polygon(new List<Vector2> { D, A, B, C });
                }
            }

            base.HandleInput(gameTime);
        }
        #endregion

        #region DRAW
        public override void Render(GameTime gameTime)
        {
            //
            // Draw Kinect data
            //
            //SpriteBatch.Draw(game.GetTexture("kinectrgb"), new Rectangle(0, 0, game.GameWidth, game.GameHeight), Color.White);
                            kinect.DrawSkeleton(SpriteBatch, lb, pos1, new Vector2(size.X, 128), pauseMechanics ? Color.Red : Color.White, p1Id);
            if (twoPlayer)  kinect.DrawSkeleton(SpriteBatch, lb, pos2, new Vector2(size.X, 128), pauseMechanics ? Color.Red : Color.Blue, p2Id);
            game.RestartBatch();

            //
            // Draw paddles
            //
            pad1.Draw(SpriteBatch, lb, Color.White);
            pad2.Draw(SpriteBatch, lb, Color.White);
            if (twoPlayer)
            {
                pad3.Draw(SpriteBatch, lb, Color.Blue);
                pad4.Draw(SpriteBatch, lb, Color.Blue);
            }

            //
            // Draw balls
            //
            SpriteBatch.Draw(game.GetTexture("solid"), ball.GetBoundingRect(), Color.White);

            //
            // Draw blocks
            //
            foreach (var item in blocks)
            {
                if (!item.Dead)
                    item.Render(SpriteBatch, Vector2.Zero);
            }

            base.Render(gameTime);
        }
        #endregion
    }
}
